package com.yfbao.horizon.utils;

import cn.hutool.core.io.FileUtil;
import com.yfbao.horizon.model.ClassMethodStat;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import sun.util.calendar.BaseCalendar;

import javax.annotation.PostConstruct;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

public class ScanClass {


    private static int defaultListSize=1000;
    private static int defauleMapSize = 1000;
    private static int defaultPageSize = 100;

    private static Map<String,Integer> recursionMap = new HashMap<>();
    private static String baseDir = "C:\\Users\\byf78\\Desktop\\日志分析\\接口流量测算";

    public static void main(String[] args) throws Exception {
        String file ="C:\\Users\\byf78\\Desktop\\日志分析\\接口流量测算\\jar";
        String prefix = "ctp-user";
        scanTarClass(file,prefix);
    }

    private static void scanTarClass(String file,String prefix) throws Exception {
        Set<String> set = ClassHelper.scanAndGetTargetClassSet(file, prefix);
        scanController(set);

        scanDubboService(set);
    }

    private static void scanController(Set<String> set) throws Exception {
        Set<Class<?>> restAnnotaitonClazzFromSet = ClassHelper.getTargetAnnotaitonClazzFromSet(set, RestController.class);
        System.out.println("RestController class size:"+restAnnotaitonClazzFromSet.size());
        List<ClassMethodStat> statList = new ArrayList<>();
        for (Class<?> aClass : restAnnotaitonClazzFromSet) {
            getAnnotationByMethods(aClass, PostMapping.class,statList);
        }
        writeToFile(statList,baseDir+ File.separator+"rest-out.txt");
    }

    private static void scanDubboService(Set<String> set) throws Exception {
        Set<Class<?>> dubboAnnotaitonClazzFromSet = ClassHelper.getTargetAnnotaitonClazzFromSet(set, Service.class);
        System.out.println("Dubbo class size:"+dubboAnnotaitonClazzFromSet.size());
        List<ClassMethodStat> statList = new ArrayList<>();
        for (Class<?> aClass : dubboAnnotaitonClazzFromSet) {
            getByMethods(aClass,statList);
        }
        writeToFile(statList,baseDir+ File.separator+"dubbo-out.txt");
    }



    public static void getByMethods(Class<?> aClass,List<ClassMethodStat> statList) throws Exception {
        List<Method> metohdsOutObjectMethods = getMetohdsOutObjectMethods(aClass);
//        Method[] methods = aClass.getMethods();
        for (Method method : metohdsOutObjectMethods) {
            ClassMethodStat classMethodStat = new ClassMethodStat();
            classMethodStat.setClassName(aClass.getSimpleName());
            classMethodStat.setMethodName(method.getName());
            classMethodStat.setReturnValueType(method.getGenericReturnType().getTypeName());
            System.out.println("开始处理类："+aClass.getSimpleName()+", 方法："+method.getName());
            long methodResLength = getMethodResLength(method);
            System.out.println("完成处理类："+aClass.getSimpleName()+", 方法："+method.getName()+", 返回值长度评估:"+methodResLength);
            classMethodStat.setReturnLength(methodResLength);
            statList.add(classMethodStat);
        }
    }

    public static <A extends Annotation> void getAnnotationByMethods(Class<?> aClass, Class<A> annotationClass, List<ClassMethodStat> statList) throws Exception {
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            ClassMethodStat classMethodStat = new ClassMethodStat();
            classMethodStat.setClassName(aClass.getSimpleName());
            classMethodStat.setMethodName(method.getName());
            classMethodStat.setReturnValueType(method.getGenericReturnType().getTypeName());
            if(annotationClass ==null){
                long methodResLength = getMethodResLength(method);
                System.out.println("完成处理类："+aClass.getSimpleName()+", 方法："+method.getName()+", 返回值长度评估:"+methodResLength);
                classMethodStat.setReturnLength(methodResLength);
                statList.add(classMethodStat);
                continue;
            }
            if(ClassHelper.checkMetodAnnotations(method,annotationClass)){
                System.out.println("开始处理类："+aClass.getSimpleName()+", 方法："+method.getName());
                long methodResLength = getMethodResLength(method);
                System.out.println("完成处理类："+aClass.getSimpleName()+", 方法："+method.getName()+", 返回值长度评估:"+methodResLength);
                classMethodStat.setReturnLength(methodResLength);
                statList.add(classMethodStat);
            }
        }
    }

    public static long getMethodResLength(Method method) throws Exception {
        long length=0;
        recursionMap.clear();
        Type genericReturnType = method.getGenericReturnType();
        if(genericReturnType.getTypeName().equalsIgnoreCase("void")){
            return length;
        }
        //如果为含有范类型的对象
        if (genericReturnType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
            //获得Demo1<String>，<>中的实际类型参数
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                length = length+innnerCheck(actualTypeArgument);
            }
            if(parameterizedType.getRawType().getTypeName().endsWith("ListResponse")){
                //TODO: 特殊处理
                length=length*defaultListSize+8;
                System.out.println("ListResponse 待特殊处理");
            }
            if(parameterizedType.getRawType().getTypeName().endsWith("PageResponse")){
                //TODO: 特殊处理
                length = length*defaultPageSize+24;
                System.out.println("PageResponse 待特殊处理");
            }
            if(parameterizedType.getRawType().getTypeName().endsWith("SingleData")){
                //TODO: 特殊处理
                length =length+8;
                System.out.println("SingleData 待特殊处理");
            }
            if(parameterizedType.getRawType().getTypeName().endsWith("SingleResponse")){
                //TODO: 特殊处理
                length =length+8;
                System.out.println("SingleResponse 待特殊处理");
            }
            if(parameterizedType.getRawType().getTypeName().endsWith("PageScanResponse")){
                //TODO: 特殊处理
                length =length*defaultPageSize+24;
                System.out.println("SingleResponse 待特殊处理");
            }
            return length;
            //获得参数类型
        }
        Class<?> clazz = null;
        try {
            clazz = (Class<?>) genericReturnType;
        }catch (Throwable t){
            System.out.println("不识别的类型："+genericReturnType.getTypeName());
            return length;
        }
        //TODO: 计算类中属性长度
        length = innnerCheck(clazz);;
        return length;
    }

    private static long innnerCheck(Type actualTypeArgument) throws Exception {
        long length=0;
        if (actualTypeArgument instanceof ParameterizedType) {
            if(actualTypeArgument instanceof ParameterizedTypeImpl){
                ParameterizedTypeImpl actualType = (ParameterizedTypeImpl) actualTypeArgument;
                if(actualType.getRawType().isAssignableFrom(List.class)){
                    //TODO： list 处理
                    long value = calcListOrMapRes(actualType);
                    length=length+value*defaultListSize;
                    return length;
                }
                if(actualType.getRawType().isAssignableFrom(Map.class)){
                    //TODO  MAP的处理方式
                    long value = calcListOrMapRes(actualType);
                    length=length+value*defauleMapSize;
                    return length;
                }
            }
            ParameterizedType pt = (ParameterizedType) actualTypeArgument;
            //todo:得到泛型里的class类型对象。
            Type[] ptActualTypeArguments = pt.getActualTypeArguments();
            for (Type ptActualTypeArgument : ptActualTypeArguments) {
                System.out.println(ptActualTypeArgument.getTypeName());
                length=length+innnerCheck(ptActualTypeArgument);
                continue;
            }
        }
        Class<?> clazz = null;
        try {
            clazz = (Class<?>) actualTypeArgument;
        }catch (Throwable t){
            System.out.println("不识别的类型："+actualTypeArgument.getTypeName());
            return length;
        }
        length = length+calcObjecRes(clazz);
//        if(value < 0){
//            //非基本类型，递归调用类解析方法
//            Integer integer = recursionMap.get(clazz.getSimpleName());
//            if(integer ==null && integer <=5){
//                System.out.println("二次解析："+clazz.getName());
//                length=length+ calcObjecRes(clazz);
//            }else {
//                length = length+50;
//            }
//        }else {
//            length=length+value;
//        }
        //TODO: 计算类中属性长度
//        System.out.println(clazz.getName());
        return length;
    }


    private static long calcListOrMapRes(ParameterizedTypeImpl actualType) throws Exception {
        //TODO： list 处理
        long length =0;
        Type[] listActualTypeArguments = actualType.getActualTypeArguments();
        for (Type listActualTypeArgument : listActualTypeArguments) {
            System.out.println("list:"+listActualTypeArgument.getTypeName());
            //todo: 递归循环调用获取对象长度
            length= length+innnerCheck(listActualTypeArgument);
        }
        return length;
    }



    private static long calcObjecRes(Class<?> aClass) throws Exception {
        long valueField= doCalc(aClass);
        if(valueField>=0){
            return valueField;
        }
        long length = 0;
        Field[] fields = aClass.getDeclaredFields();//获取字段信息
        for (Field f : fields) {
            Class<?> fieldClazz = f.getType();//获取f的类
            if (fieldClazz.isPrimitive()) {
                length= length + doPrimitaiveCalc(fieldClazz);
                continue;  //判断是否为基本类型
            }
            if (fieldClazz.isAssignableFrom(List.class)) {//判断fc是否和List相同或者其父类
                Type fc = f.getGenericType(); //如果是List类型，得到其Generic的类型
                if (fc instanceof ParameterizedType) {
                    ParameterizedTypeImpl pt = (ParameterizedTypeImpl) fc;
                    //得到泛型里的class类型对象。
                    long value = calcListOrMapRes(pt);
                    length=length+value*defaultListSize;
                    continue;
                }
            }
            if (fieldClazz.isAssignableFrom(Map.class)) {//判断fc是否和List相同或者其父类
                Type fc = f.getGenericType(); //如果是List类型，得到其Generic的类型
                if (fc instanceof ParameterizedType) {
                    ParameterizedTypeImpl pt = (ParameterizedTypeImpl) fc;
                    //得到泛型里的class类型对象。
                    long value = calcListOrMapRes(pt);
                    length=length+value*defauleMapSize;
                    continue;
                }
            }
            if (fieldClazz.isAssignableFrom(Object.class)) {
                continue;
            }

            long value = doCalc(fieldClazz);
            if(value<0){
                //非基本类型，递归调用类解析方法
                Integer integer = recursionMap.get(aClass.getSimpleName());
                if(integer ==null && integer <=5){
                    System.out.println("二次解析对象："+fieldClazz.getName());
                    length=length+ innnerCheck(fieldClazz);
                }else {
                    length = length+50;
                }
            }else {
                length=length+value;
            }
        }
        return length;
    }

    /**
     * * @see     java.lang.Boolean#TYPE 1/4
     * * @see     java.lang.Character#TYPE 2
     * * @see     java.lang.Byte#TYPE 1
     * * @see     java.lang.Short#TYPE  2
     * * @see     java.lang.Integer#TYPE   4
     * * @see     java.lang.Long#TYPE 8
     * * @see     java.lang.Float#TYPE 4
     * * @see     java.lang.Double#TYPE 8
     * * @see     java.lang.Void#TYPE 0
     *
     * @param aClass
     */
    static long doCalc(Class<?> aClass) {
        if(aClass.isPrimitive()){
            return doPrimitaiveCalc(aClass);
        }
        String simpleName = aClass.getSimpleName();
        long length = 0;
        if (aClass.isAssignableFrom(Boolean.class)) {
            length = length + 4 + 4;
        } else if (aClass.isAssignableFrom(Character.class)) {
            length = length + 2 + 4;
        } else if (aClass.isAssignableFrom(Byte.class)) {
            length = length + 1 + 4;
        } else if (aClass.isAssignableFrom(Short.class)) {
            length = length + 2 + 4;
        } else if (aClass.isAssignableFrom(Integer.class)) {
            length = length + 4 + 4;
        } else if (aClass.isAssignableFrom(Long.class)) {
            length = length + 8 + 4;
        } else if (aClass.isAssignableFrom(Float.class)) {
            length = length + 4 + 4;
        } else if (aClass.isAssignableFrom(Double.class)) {
            length = length + 8 + 4;
        } else if (aClass.isAssignableFrom(String.class)) {
            length = length + 50 + 4;
        } else if (aClass.isAssignableFrom(Date.class)) {
            length = length + 7;
        }else if(simpleName.endsWith("Enum") && aClass.getSuperclass().isAssignableFrom(Enum.class)){
            //TODO:枚举特殊处理
            length = length + 4+4;
        }else if(aClass.getSimpleName().endsWith("Object")){
            //Object对象统一按照20字节评估
            length = length + 20;
        }
//        else if(simpleName.endsWith("CtpResourceDto")){
//            //特殊类处理
//            length = length+13*8+17*50+3*4+4*8+7;
//        }else if(simpleName.endsWith("CtpNavMenuDto")){
//            //特殊类处理
//            length = length+4*8+15*50+3*4+3*8;
//        }
        else if (aClass.isAssignableFrom(Class.class)
                || aClass.isAssignableFrom(Constructor.class)
                || aClass.isAssignableFrom(BaseCalendar.class)) {
            return 0;
        }else{
            System.out.println("unknow class:" + simpleName);
            Integer integer = recursionMap.get(simpleName);
            if(integer ==null){
                recursionMap.put(simpleName,1);
            }else {
                if(integer>5){
                    return 100;
                }
                recursionMap.put(simpleName,integer+1);
            }
            return -1;
        }
        return length;
    }

    private  static long doPrimitaiveCalc(Class<?> aClass) {
        long length = 0;
        if (aClass.isAssignableFrom(boolean.class)) {
            length = length + 4;
        } else if (aClass.isAssignableFrom(char.class)) {
            length = length + 2;
        } else if (aClass.isAssignableFrom(byte.class)) {
            length = length + 1;
        } else if (aClass.isAssignableFrom(short.class)) {
            length = length + 2;
        } else if (aClass.isAssignableFrom(int.class)) {
            length = length + 4;
        } else if (aClass.isAssignableFrom(long.class)) {
            length = length + 8;
        } else if (aClass.isAssignableFrom(float.class)) {
            length = length + 4;
        } else if (aClass.isAssignableFrom(double.class)) {
            length = length + 8;
        }else {
            System.out.println("unknow Primitaive:" + aClass.getName());
        }
        return length;
    }


    private static void writeToFile(List<ClassMethodStat> list,String outfile){
        List<String> collect = list.stream().map(a -> a.toString()).collect(Collectors.toList());
        FileUtil.appendLines(collect,outfile, Charset.defaultCharset());
    }


    private static List<Method> getMetohdsOutObjectMethods(Class<?> clazz){
        List<Method> objectMetohds = getObjectMethods(clazz);
        List<Method> arrayList = getObjectMethods(Object.class);
        Iterator<Method> iterator = objectMetohds.iterator();
        loop: while (iterator.hasNext()){
            Method next = iterator.next();
            for (Method method : arrayList) {
                if(method.getName().equals(next.getName())){
                    iterator.remove();
                    continue loop;
                }
            }
        }
        return objectMetohds;
    }
    private static List<Method> getObjectMethods(Class<?> clazz){
        Method[] declaredMethods = clazz.getDeclaredMethods();
        List<Method> arrayList = new ArrayList<>();
        for (Method declaredMethod : declaredMethods) {
            arrayList.add(declaredMethod);
        }
        return arrayList;
    }
}
